package netsi;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;


import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;


/**
 * Receiver initiates connection to server
 * @author Bao To & Chris Yereaztian
 *
 */
public class Receiver{
	
	public static final Logger logger = Logger.getLogger("Receiver");
	
	//Alice's secret number, this is inherently insecure
	//should use java.security.KeyPairGenerator; to generate random secret
	//this is only for demonstration purposes
	private final int bobssecret = 5;
	//DH Parameters
	private BigInteger p;
	private BigInteger g;
	
	private Socket client;
	private ObjectInputStream oin;
	private ObjectOutputStream oout;
	
	private BigInteger sharedSecret;
	
	private long posInText;
	private int OTPKeyLength;
	
	public static final String keytext = "res/crsto10.txt";
	
	/**
	 * Constructor assumes that Receiver and Sender are run
	 * on the same host so the sender class is accessible.
	 * Otherwise please us specific constructor and provide address and port
	 * of the server
	 * @throws UnknownHostException 
	 */
	public Receiver() throws UnknownHostException{
		this(InetAddress.getByName("localhost"), Sender.LISTENPORT);
	}
	
	public Receiver (InetAddress address,int port){
		try{
			client = new Socket(address, port);
			logger.info("Connected to: " + client.getRemoteSocketAddress());
			oin = new ObjectInputStream(client.getInputStream());
			oout = new ObjectOutputStream(client.getOutputStream());
			oout.flush();
			logger.info("Exchanging Keys...");
			exchangeSessionKey();
			receiveMessageFromSocket();
		}
		catch (IOException e){
			logger.info("Could not open Socket to establish connection: "+ e.getMessage());
		}
		
	}
	
	public String readOTPKeyFromFile(int keylength){
		//TODO: Implementation
		FileReader fin;
		char[] key = new char[keylength];
		try {
			fin = new FileReader(keytext);
			fin.skip(posInText);
			BufferedReader reader = new BufferedReader(fin);
			int count = reader.read(key);
			if (count < keylength || count == -1){
				logger.error("Error Reading Key: Reached End of Stream - consider repositiong");
			}
			logger.debug("Key: " + new String(key));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new String(key);
	}
	private void exchangeSessionKey() {
		//establish connection with server to exchange p and g
		try{
			//receiving p and g
			p = (BigInteger)oin.readObject();
			g = (BigInteger)oin.readObject();
			logger.info("Received prime: " + p);
			logger.info("Received generator: " + g );
			//receiving A = g^alicesecret mod p (alicesecret is not transmitted)
			BigInteger ga = (BigInteger)oin.readObject();
			logger.info("Received Alice's g^a mod p : " + ga);			
			//sending B = g^bobssecret mod p (bobssecret is not transmitted)
			BigInteger gb = g.pow(bobssecret).mod(p);
			logger.info("Sending gb: "+gb);
			oout.writeObject(gb);
			oout.flush();
			//Calculate shared secret gbit ^ alicesecret mod p;
			sharedSecret = ga.pow(bobssecret).mod(p);
			logger.debug("Shared Secret: "+ sharedSecret);
			
			//exchanging OTP Text Position
			//cheap encryption: calculate A = sharedsecret ^ postext
			//Bob can easily calculate postext with A ^ sharedsecret = postext
			//known only by Alice and Bob
			BigInteger encryptedPos = (BigInteger)oin.readObject();
			logger.info("received encrypted TextPos:" + encryptedPos);
			//Decrypting TextPos
			posInText = encryptedPos.xor(sharedSecret).longValue();
			logger.info("Decrypted TextPos: "+ posInText);
			BigInteger encryptedOTPKeyLength = (BigInteger)oin.readObject();
			logger.info("received encrypted OTPKeyLength: " + encryptedPos);
			//Decrypting TextPos
			OTPKeyLength = (int) encryptedOTPKeyLength.xor(sharedSecret).longValue();
			logger.info("Decrypted OTPKeyLength: "+ OTPKeyLength);
			
			
		 }
		catch(IOException io){
			logger.info("Broken Pipe: Client must have disconnected:" + io.getMessage());
			io.printStackTrace();
		}
		catch (ClassNotFoundException e) {
			logger.debug("ClassNotFound Exception: something went wrong while" +
					"trying to read object from networkstream");
		 }
	}
	
	private String receiveMessageFromSocket(){
		String msg = new String();
		try {
			msg = (String)oin.readObject();
			logger.info("Received Encrypted Msg: "+ msg);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String otpkey = readOTPKeyFromFile(OTPKeyLength);
		logger.info("OTPKey read from file: "+otpkey);
		//Decrypt msg
		String decryptedmsg = decryptText(otpkey, msg);
		logger.info("Decrypted Msg: "+ decryptedmsg);
		//close streams
		try {
			client.shutdownInput();
			client.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
		return decryptedmsg;
	}
	
	public String decryptText(String cipherkey, String message){
		//check that the Strings are of equal length
		if (cipherkey.length() != message.length())
			logger.warn("Key and Message are not of equal length - message will partially be transmitted in plain text");
		//XORing the two strings at character level
		StringBuilder sb = new StringBuilder();
		for(int i=0; i<cipherkey.length() && i<message.length();i++)
		    sb.append((char)(cipherkey.charAt(i) ^ message.charAt(i)));
		String result = sb.toString();
		logger.debug("Decrypted Text: " + result);
		return result;
	}
	
	/**
	 * @param args
	 * @throws UnknownHostException 
	 */
	public static void main(String[] args) throws UnknownHostException {
		// Set up a simple configuration that logs on the console.
		BasicConfigurator.configure();
		logger.setLevel(Level.DEBUG);
		new Receiver();
	}
}
